/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

/***********************************************************************
 *
 * FILENAME:
 * gtk_gpsmap.c
 *
 * PURPOSE:
 *	Provide a GPS map interface to the user.
 *
 * LAST MODIFIED:
 *
 ***********************************************************************/

#ifndef __GTKGPS_MAP_H
#define __GTKGPS_MAP_H

class LayerManager;

#include <gtk/gtk.h>
#include <goocanvas.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <libsoup/soup.h>

#include "geoImage.h"
#include "geoVideo.h"
#include "gpsWaypoint.h" // for waypoint plans
#include "clientList.h"  // for MAX_CLIENTS
#include "LayerManager.h"
#include "layerList.h"  

#include "geocoder.h"  

//------- Defines ---------//
#define GUI_MAPS_DIR  VCGUI_DIR"/maps"

#define TILESIZE 256
#define MAX_ZOOM 20
#define MIN_ZOOM 1

//---------------------------//
#define LEFT_MOUSE            1
#define MIDDLE_MOUSE          2
#define RIGHT_MOUSE           3
#define MAX_NUM_GEOIMG        10
#define DEFAULT_DEG_PER_PIXEL 2.0e-4
#define ANIMATION_TIME        500 
#define ANIMATION_STEPS       100 
#define MAP_ANIMATION_TIME    200 
#define MAP_ANIMATION_STEPS   5 

//------- Structs ---------//
typedef struct _GeoImgAlloc {
	double         x_off;    // x offset of img relative to geoImg[0]
	double         y_off;    // y offset of img relative to geoImg[0]
	//double         width;  // scaled img width
	//double         height; // scaled img height
	double         scale_x;    // scale from this geo img to the one at geoImg[0]
	double         scale_y;    // scale from this geo img to the one at geoImg[0]
	GeoImage     *geoImg;   // geo referenced image
	GeoVideo     *geoVideo;   // geo referenced image
	//volatile bool  is_shown; // if this map is currently displayed
	GooCanvasItem *img_item;
} GeoImgAlloc;


typedef enum {
	OSM_MAPNIK = 0,
	OSM_RENDERER,
	OSM_CYCLE,
	OSM_MAPLINT,
	OPENAERIALMAP,
	GOOGLE_MAPS,
	GOOGLE_TOPO,
	GOOGLE_HYBRID,
	GOOGLE_SATELLITE,
	GOOGLE_SATELLITE_QUAD,
	GOOGLE_DYNAMIC,
	GOOGLE_DYNAMIC_MOBILE,
	GOOGLE_DYNAMIC_SAT,
	GOOGLE_DYNAMIC_TERRAIN,
	GOOGLE_DYNAMIC_HYBRID,
	MAPS_FOR_FREE,
	VIRTUAL_EARTH,
	VIRTUAL_EARTH_HYBRID,
	VIRTUAL_EARTH_SATELLITE,
	RUNWAYFINDER,
	NUM_MAP_SOURCE
} MapSource;

#define DEFAULT_MAP GOOGLE_MAPS

//------- GPSMap class ---------//
class GPSMap 
{
	public:

	typedef enum {
		MAP_SHOW_SCROLLBARS,
		MAP_AUTO_SCROLLBARS,
		MAP_NO_SCROLLBARS
	} ScrolledMode;
	
	typedef enum {
		BASE_LAYER = 0,
		OSM_LAYER,
		MAP_LAYER,
		RADAR_LAYER,
		RADAR_SIM_LAYER,
		LEVEL2_LAYER,
		NWS_WARN_LAYER,
		FB_LAYER,      // flight boundary layer
		NETWORK_LAYER,
		WIND_LAYER,
		FP_LAYER,      // flight plan layer
		SASSI_LAYER,
		ICON_LAYER,
		DM_LAYER,
		TARGET_LAYER,
		NUM_LAYERS
	} GroupLayers;

		GPSMap();
		~GPSMap();

		// Initialization and drawing primitives 
		bool Init(GtkWidget *parent);
		bool initOSM(MapSource sourceInd = NUM_MAP_SOURCE );

		// where the mouse lat/lon is displayed
		bool set_mouse_info_location(GtkStatusbar *mouse_location);

		//-- geo images --//
		int add_geoImage(String file); // returns number of map images, or -1 if error
		void clear_geoImage(); // returns number of map images, or -1 if error
		//String getBaseImage(); 

		//-- geo video --//
		int add_videoImage(int cam, GdkPixbuf *pixbuf);
		void update_videoImage(int mapCount, GdkPixbuf *pixbuf);

		int getNumRaster() { return mapCount; }
		String getRasterFile(int imgInd);

		//-- flight plans --//
		bool add_wp(String file);  // true if file loaded
		bool save_wp(String file); // true if file saved
		bool creating_plan;
		bool creating_dist;
		void setPlanEditing( bool val );
		void setDistEditing( bool val );

		static int find_closest(GooCanvasPoints *points, double drag_x, double drag_y);

		//-- Layers --//
		String        layer_names[NUM_LAYERS];
		LayerManager *layer_manager[NUM_LAYERS];
		LayerList     layer_list;

		void setLayerListShown(GooCanvasItem *item, bool val)
				{ layer_list.setShown(item, val); }
		void addLayer(String name, GooCanvasItem *item, GooCanvasItem *parent=NULL, const char *tooltip=NULL) 
				{ layer_list.addLayer(name, item, parent, tooltip); }
		void delLayer( GooCanvasItem *item ) 
				{ layer_list.delLayer(item); }

		bool toggleLayerShown(GroupLayers g, int setTo = -1);
		bool toggleLayerZooming(GroupLayers g, int setTo = -1);

		GooCanvasGroup *getLayerG(GroupLayers g) { return layerG[g]; }

		//-- gui --//
		GooCanvas      *getCanvas() { return canvas; } 
		GooCanvasItem  *getRootItem() { return root_item; } 
		GtkWidget      *getMainWin() { return window; } // parent widget
		GtkUIManager   *get_MenuManager() { return menu_manager; }
		GtkWidget      *getToolBar() { return gtk_ui_manager_get_widget (menu_manager, "/Toolbar"); }

		void setup_scale();
		GooCanvasItem *scale_item;

		//-- Animation --//
		bool animateMotion() { return animate_motion; }
		bool animateZoom() { return animate_zoom; }

		int getWidth()  { if( window ) return window->allocation.width; else return -1; }
		int getHeight() { if( window ) return window->allocation.height; else return -1; }
		double getMapLat();
		double getMapLon();

		static gboolean update_map_animation(gpointer data);
		int animation_count;

		//-- zoom functionality --//
		bool zoomFit(); // fit every known object to the drawable window
		bool zoomIn(bool linear = true); // zoom in, linearly if set, about 0,0
		bool zoomOut(bool linear = true); // zoom out, linearly if set, about 0,0
		bool zoomInGeo(double x, double y, bool linear = true ); // zoom in, linearly if set, about geo point
		bool zoomOutGeo(double x, double y, bool linear = true); // zoom out, linearly if set, about geo point
		bool zoomInWin(bool linear = true ); // zoom in, linearly if set, about center of window
		bool zoomOutWin(bool linear = true); // zoom out, linearly if set, about center of window
		bool zoomReset(); // reset zoom
		bool zoomInLevel(int level);
		int  getMapZoom() {  return map_zoom; }

		void stopNodeTracking();
		void moveToGeo(double lon, double lat, bool animate=false); // set center of map to geo

		bool zoomIn_nofill(bool linear = true); // zoom in, linearly if set, about 0,0
		bool zoomOut_nofill(bool linear = true); // zoom out, linearly if set, about 0,0

		void reset_translation(double dx = 0.0, double dy = 0.0);

		//-- coordinate transforms --//
		bool windowToGeo(double &x, double &y); // window to geographic [px, px]   -> [deg, deg]
		bool geoToWindow(double &x, double &y); // geographic to window [deg, deg] -> [px, px]
		bool geoToLayer(GroupLayers g, double &x, double &y);
		bool layerToGeo(GroupLayers g, double &x, double &y);

		double degPerXp();                  // geographic to window
		double degPerYp( double lat= NAN ); // geographic to window
		double mPerP( double lat=NAN );     // meter/pixel at latitude

		//-- event and signal handlers --//
		//  window events
		static int window_delete_event_cb(GtkWidget* window, GdkEventAny* e, GPSMap *map);
		static int window_key_event_cb(GtkWidget *widget, GdkEventKey *event, GPSMap *map);
		static int window_expose_event_cb(GtkWidget *widget, GdkEventExpose *event, GPSMap *map);
		static int window_button_event_cb(GtkWidget *widget, GdkEventButton *event, GPSMap *map);
		static int window_motion_event_cb(GtkWidget *widget, GdkEventMotion *event, GPSMap *map);
		static int mouse_scroll_event_cb(GtkWidget *widget, GdkEventScroll *event, GPSMap *map);

		//  item events
		static bool item_button_press_event_cb (GooCanvasItem *item, GooCanvasItem *target_item, 
		                  GdkEventButton *event, GPSMap *map);
		static bool item_button_release_event_cb (GooCanvasItem  *item, GooCanvasItem  *target_item, 
		                  GdkEventButton *event, GPSMap *map);
		static bool item_motion_notify_event_cb (GooCanvasItem  *item, GooCanvasItem  *target_item, 
		                  GdkEventMotion *event, GPSMap *map);
		static bool item_key_event_cb (GooCanvasItem  *item, GooCanvasItem  *target_item, 
		                  GdkEventKey *event, GPSMap *map);


		// control window made by middle mouse click
		int create_control_window(GdkEventButton *event);
		
		// these are menu callbacks for the menue
		static int load_image_cb(GtkAction *action, GPSMap *me);
		static int clear_maps_cb(GtkAction *action, GPSMap *me);

		static int zoom_in_cb(GtkAction *action, GPSMap *me);
		static int zoom_out_cb(GtkAction *action, GPSMap *me);
		static int zoom_reset_cb(GtkAction *action, GPSMap *me);
		static int zoom_fit_cb(GtkAction *action, GPSMap *me);

		static int load_wp_cb(GtkAction *action, GPSMap *me);
		static int clear_wp_cb(GtkAction *action, GPSMap *me);
		static int create_wp_cb(GtkToggleAction *action, GPSMap *me);
		static int create_dto_cb(GtkAction *action, GPSMap *me);
		static int create_dlc_cb(GtkAction *action, GPSMap *me);
		static int load_fb_cb(GtkAction *action, GPSMap *me);
		static int clear_fb_cb(GtkAction *action, GPSMap *me);
		static int clear_dist_cb(GtkAction *action, GPSMap *me);

		static int osm_mapserver_cb(GtkRadioAction *action, GtkRadioAction *current, GPSMap *me);
		static int units_cb(GtkRadioAction *action, GtkRadioAction *current, GPSMap *me);

		static int toggle_animation(GtkToggleAction *action, GPSMap *me);
		static int map_search_cb(GtkAction *action, GPSMap *me);
		static int map_dist_cb(GtkToggleAction *action, GPSMap *me);

		static gboolean search_window_destroy(GtkWidget * widget, GdkEventKey * event, gpointer user_data);
		static void search_button_clicked(GtkButton *button, gpointer user_data);

		// search control window
		GtkWidget  *search_window;
		GtkWidget *apply_button;
		GtkWidget *cancel_button;
		//GtkEntry   *state_entry;
		//GtkEntry   *city_entry;
		GtkEntry   *search_entry;
		void create_search_window();

		Geocoder geocoder;

		// user pop-up file selection windows go to last directory used
		void set_last_directory_from_filename(const gchar *filename);
		String get_last_directory() { return last_directory; }
		static void create_error_window(GPSMap *me, const char *);

	protected:

		// main window interface
		GtkWidget    *window; // parent widget
		GtkWidget    *scrolled_win; // scrollable window interface
		ScrolledMode  scrolled_mode;
		GooCanvas    *canvas; // main canvas widget

		// widgets for which I display information
		GtkStatusbar *mouse_status; // place to put mouse pointer information

		// dialog and control window interfaces
		String          last_directory;
		GtkItemFactory *item_factory;

		// menu  widget
		String          menu_ui_file;
		GtkWidget      *menu;
		GtkWidget      *toolbar_menu;
		GtkActionGroup *action_group;          /* Packing group for our Actions */
		GtkUIManager   *menu_manager;          /* The magic widget! */


		// groups -- aka layers for us. we have a base layer for every thing, and
		// is what is panned by the mouse, the root_item. We do not use the built in zoom
		// functionality because we don't want some items to be scaled at times.
		// then we have the follow groups (layers). note they can be drawn in any
		// order desired by user. They are all attached to the root item.
		//            --  map group (layer).
		//            --  flightplan group (layer).
		//            --  icon group (layer).
		GooCanvasItem  *root_item;
		GooCanvasItem  *movable_root;

		GooCanvasGroup *osm_map_layer;  // layer groups

		bool layer_is_shown[NUM_LAYERS];     // if layer is shown
		GooCanvasGroup *layerG[NUM_LAYERS];  // layer groups
		GooCanvasGroup *mapG; // convience pointer
		GooCanvasGroup *zoom_fit_group; // everything we want to zoom fit to

		// Geo referenced map images
		int mapCount; // number of loaded maps
		GeoImgAlloc geoMap[MAX_NUM_GEOIMG];

		// map display variables
		//double base_x; // x coordinate for base group in the world
		//double base_y;

		// zooming
		double zoom_level;     // zoom level bounded by [MIN_ZOOM,MAX_ZOOM]
		bool layer_zooming[NUM_LAYERS];
		bool adjust_zoom(double zoom_level); // only funciton to actually adjust zoom_level

		// mouse variables
		GooCanvasItem* drag_item; // dragging item on map
		int            drag_point_ind; // index of point i am draggings
		double item_x; // x coordinate or item during last drag (item relative)
		double item_y;

		int left_mouse_pressed;
		double left_mouse_x; // x coordinate when left mouse pressed
		double left_mouse_y; // y coordinate when left mouse pressed

		GooCanvasItem* uav_item;

		bool animate_motion;
		bool animate_zoom;


		//------------------------------------
		// OSM
		//------------------------------------
		void    inspect_map_uri();
		gchar   *replace_map_uri(const gchar *uri, int zoom, int x, int y, int source);

		void fill_map_tiles();
		void blit_tile( GdkPixbuf *pixbuf, int offset_x, int offset_y);
		static void tile_download_complete (SoupSession *session, SoupMessage *msg, gpointer user_data);
		void download_tile (int zoom, int x, int y, int offset_x, int offset_y);
		void load_tile(int zoom, int x, int y, int offset_x, int offset_y);

		void convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel,
			gchar *buffer, const gchar initial,
			const gchar *const quadrant);

		int map_x;
		int map_y;
		int map_zoom;

		gboolean map_auto_download;
		GHashTable *tile_queue;
		GHashTable *missing_tiles;
		unsigned int numDownloading;

		//how we download tiles
		SoupSession *soup_session;
		char *proxy_uri;
		GdkCursor *busy_cursor;

		//where downloaded tiles are cached 
		gboolean map_auto_cache;
		char *cache_dir;

		//contains flags indicating the various special characters
		//the uri string contains, that will be replaced when calculating
		//the uri to download.
		const char *repo_uri;
		int uri_format;

		//flag indicating if the map source is located on the google
		gboolean the_google;

		MapSource mapSource;	

		//-- private function --//
		void setup_item(GooCanvasItem *item);
		
};

typedef struct {
	/* The details of the tile to download */
	char *uri;
	char *folder;
	char *filename;
	/* The area on the screen to redraw when it arrives */
	int offset_x;
	int offset_y;
	GPSMap *map;
} tile_download_t;

typedef struct {
	MapSource  source;
	const char      *name;
	const char      *uri;
	int        min_zoom;
	int        max_zoom;
	const char      *fig_type;
} map_source_t;


#endif // _GTKGPS_MAP_H
